/* Soubor je ulozen v kodovani UTF-8.
 * Kontrola kódování: Příliš žluťoučký kůň úpěl ďábelské ódy.
 */
package cz.pecinovsky.english.pojj.adv_demo.gui._03_menu;

import cz.pecinovsky.english.pojj.adv_framework.game_gui.IGameG;
import cz.pecinovsky.english.pojj.adv_framework.game_gui.IUIG;

import cz.pecinovsky.english.pojj.adv_framework.game_txt.IBag;
import cz.pecinovsky.english.pojj.adv_framework.game_txt.IGame;
import cz.pecinovsky.english.pojj.adv_framework.game_txt.IObject;
import cz.pecinovsky.english.pojj.adv_framework.game_txt.IPlace;

import cz.pecinovsky.english.pojj.adv_framework.gui_stage.IGUI_02;

import cz.pecinovsky.english.pojj.adv_framework.test_util._Test_101;
import cz.pecinovsky.english.pojj.adv_framework.test_util._Test_115;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.util.Collection;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;

import javax.swing.text.Document;



/*******************************************************************************
 * Instance třídy {@code GUI_03_1} představují
 * jednoduché grafické uživatelské rozhraní umožňující hrát hry
 * vyhovující podmínkám rámce (frameworku) definovaného v balíčku
 * {@code cz.pecinovsky.english.pojj.adv_framework.game_txt},
 * tj. textové hry vyhovující požadavkům minulého semestru.
 * <p>
 * Tato třída sice formálně implementuje rozhraní {@link IGameG},
 * ale stále ještě není jeho plnohodnotnou implementací,
 * protože je schopna (a hlavně ochotna) obsluhovat i hry implementující
 * rozhraní {@link IGame}, a proto není pravda, že:
 * "<i>nejprve však zkontroluje, že zadaná hra je ve skutečnosti instancí
 * rozhraní {@link IGameG}, protože jiné neposkytují metody, které toto
 * grafické uživatelské rozhraní očekává</i>".
 * <p>
 * Oproti verzi {@code GUI_03_0} se změnilo:
 * <ul>
 *   <li>Byly odstraněny konstanty {@code PŘED_PŘÍKAZEM} a {@code ZA_PŘÍKAZEM}
 *     protože jejich zvýrazňovací funkce byla nahrazena formátováním.
 *   </li>
 *   <li>Byla přidána konstanta {@code OUTPUT_SIZE} definující výchozí
 *     velikost výstupního pole.
 *   </li>
 *   <li>Výstupní oblast {@code #outputArea}změnila typ komponenty
 *     z {@code JTextArea} na {@code JEditorPane}, která (mimo jiné) umožňuje
 *     formátování zobrazovaného textu pomocí HTML značek.
 *   </li>
 *   <li>Protože text formátovaný prostřednictvím HTML značek musí začínat
 *     a končit příslušnými značkami, nemá komponenta {@code JEditorPane}
 *     definovanou metodu {@code append(???)}. Byl proto definován atribut
 *     {@code #stringBuilder}, v němž bude postupně kumulován vystupující text¨
 *     a jehož obsah budepředán výstupní komponentě spolu se závěrečnou zančkou.
 *   </li>
 *   <li>Vzhledem ke změně vlastností komponenty pro výstup
 *     byly příslušně upraveny i metody pro přípravu, inicializaci a 
 *     akutalizaci výstupní oblasti:
 *     <ul>
 *       <li>Metoda {@code #prepareOutputPane()} nastavuje výchozí velikost
 *         výstupní oblasti, protože již není možno tuto velikost
 *         nastavit v konstoru výstupní textové oblasti.
 *       </li>
 *       <li>Metoda {@code #initializeOutputPane()} vyčistí 
 *         {@code stringBuilder} avloží do něj úvodní značku {@code <html>}.
 *       </li>
 *       <li>Metoda {@code #updateOutputPane()} doznala největších změn,
 *         protože zcela jinak formátuje vystupující text a navíce jej
 *         musí vytvářet stranou v proměnné {@code stringBuilder},
 *         a teprve vytvořený text vložit do výstupní textové oblasti.
 *         Navíc bylo nutno změnit i způsob zjištění pozice kurzoru
 *         umisťovaného na konec vystupujícícho textu.
 *       </li>
 *     </ul>
 *   </li>
 *   <li>Do aplikačního okna byla přidána hlavní nabídka (menu bar)
 *     spolu s metodou {@code #prepareMenu()} volanou z konstruktoru.
 *   </li>
 *   <li>Jednotlivé nabídky tvořící hlavní nabídku a vytvářené v metodě
 *     {@code #prepareMenu()} jsou definovány jako instance samostatných tříd, 
 *     čímž se zvětší přehlednost programu (prozatím je definována pouze třída
 *     {@code MenuGame}).
 *   </li>
 *   <li>Konstruktorům těchto tříd je předán odkaz na instanci GUI,
 *     která je vyvolala. Účelem je zajistit, aby vytvářené instance nabídky
 *     mohly v budoucnu snadno získat všechny informace, 
 *     které budou potřebovat ke zdárnému provedení svých úkolů.
 *   </li>
 *   <li>Byla definována přístupová metoda {@code #getGame()}
 *     vracející odkaz na hru aktuálně obsluhovanou daným GUI.
 *     Účelem je opět umožnitt nabídkám získat odkaz na tuto hru.
 *   </li>
 * </ul>
 *
 * @author    Rudolf PECINOVSKÝ
 * @version   0.00.000
 */
public class GUI_03_1 implements IUIG, IGUI_02
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Souřadnice, na které se aplikační okno přesune po spuštění GUI.
     *  To má smysl především při zobrazování na sekundárním monitoru. */
    private static final Point POINT_0 = new Point(0, 0);       //(-600, 0);

   /** Preferovaná velikost výtstupní oblasti. */
    private static final Dimension OUTPUT_SIZE = new Dimension(300, 300);

    /** Odstup mezi jednotlivými prvky ve vodorovně uspořádaném panelu. */
    private static final int VODOROVNÝ_ODSTUP = 20;

    /** Odstup mezi jednotlivými prvky ve svisle uspořádaném panelu. */
    private static final int SVISLÝ_ODSTUP = 10;



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Aplikační okno spuštěného GUI. */
    private final JFrame appWindow = new JFrame("První okno");

    /** Komponenta, do které je zapisován text
     *  informující uživatele o průběhu hry. */
    private final JEditorPane outputArea = new JEditorPane("text/html", "");

    /** Objekt určený pro postupné skládání jednotlivých odpovědí hry
     *  do jednoho souvislého textového řetězce. */
    private final StringBuilder stringBuilder = new StringBuilder();

    /** Vstupní textové pole, do nějž uživatel zapisuje své příkazy. */
    private final JTextField inputField = new JTextField(25);

    /** Posluchač vstupního pole ragující na zadání příkazů uživatele,
     *  resp. na externí zadání příkazů prostřednictvím metody
     *  {@link #executeCommand(String)} */
    private final ActionListener inputListener = new TxfListener();

    /** Panel zborazující obsah batohu. */
    private final Box bagPane = new Box(BoxLayout.X_AXIS);

    /** Návěští v panelu batohu specifikující obsah daného panelu. */
    private final JLabel bagTitle = new JLabel("Obsah batohu:");

    /** Panel zobrazující sousedy prostoru, v němž se hráč nachází. */
    private final Box neighborPane = new Box(BoxLayout.Y_AXIS);

    /** Návěští v panelu sousedů specifikující obsah daného panelu. */
    private final JLabel neighborTitle = new JLabel("===== Sousedé =====");

    /** Panel zborazující objekty nacházející se v prostoru,
     *  v němž se hráč nachází. */
    private final Box objectPane = new Box(BoxLayout.Y_AXIS);

    /** Návěští v panelu objektů specifikující obsah daného panelu. */
    private final JLabel objectTitle = new JLabel("===== Objekty =====");



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Hra, která bude hrána prostřednictvím tohoto GUI. */
    private IGame thisGame;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří novou instanci GUI.
     * Po dokončení konstruktoru se však ještě nenakreslí;
     * vykreslení aplikačního okna a jeho součástí spustí až metoda,
     * která spouští zadanou hru, od níž se teprve dozví, co zobrazovat.
     */
    public GUI_03_1()
    {
        appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        prepareMenu();

        prepareOutputPane();
        prepareInputPane();
        prepareNeighborPane();
        prepareObjectPane();
        prepareBagPane();
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================

    /***************************************************************************
     * Vrátí jméno autora scénáře ve formátu <b>PŘÍJMENÍ Křestní</b>,
     * tj. neprve příjmení psané velkými písmeny a za ním křestní jméno,
     * u nějž bude velké pouze první písmeno a ostatní písmena budou malá.
     *
     * @return Jméno autora/autorky programu ve tvaru PŘÍJMENÍ Křestní
     */
    @Override
    public String getAuthor()
    {
        //Náhraní řešení: jméno autora implicitní hry
        return _Test_115.getDefaultGame().getAuthor();
    }


    /***************************************************************************
     * Vrátí x-name autora/autorky programu zapsané velkými písmeny.<p>
     * X-name má tvar <b>{@code XPPPK99}</b> nebo <b>{@code XPPPK999}</b>,
     * kde<br>
     * <ul>
     *   <li><b>{@code PPP}</b> zastupuje první tří písmena příjmení autora(ky)
     *      zapsaná velkými písmeny, </li>
     *   <li><b>{@code K}</b> zastupuje první písmeno křestního jména a </li>
     *   <li><b>{@code 99}</b>, resp. <b>{@code 999}</b> zastupují číslice
     *      přiřazené systémem, pomocí nichž se rozsliší studenti(ky)
     *      s předchozími znaky shodnými.
     * </ul>
     *
     * @return X-name autora/autorky programu
     */
    @Override
    public String getXNAME()
    {
        //Náhraní řešení: x-name autora implicitní hry
        return _Test_115.getDefaultGame().getXNAME();
    }


    /***************************************************************************
     * Vrátí odkaz na naposledy/právě hranou hru.
     *
     * @return Odkaz na naposledy/právě hranou hru
     */
    public IGame getGame()
    {
        return thisGame;
    }



//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Spustí komunikaci mezi zadanou hrou a danou instancí GUI
     * mající na starosti komunikaci s uživatelem.
     * <p>
     * Metoda před pokusem o spuštění zadané hry nejprve zkontroluje,
     * že pomocí daného GUI není akutálně hrána žádná hra
     * a že ani zadaná hra není rozehrána. V opačném případě vyhodí výjimku.
     * <p>
     * Tato verze ještě není plnohodntou implementací,
     * protože je schopna obsluhovat i hry implementující rozhraní
     * a proto není pravda, že: "<i>nejprve však zkontroluje,
     * že zadaná hra je ve skutečnosti instancí rozhraní {@link IGameG},
     * protože jiné neposkytují metody, které grafické UI očekává</i>".
     *
     * @param game Hra, kterou ma dané UI spustit
     * @throws IllegalArgumentException
     *         Existují důvody, které brání spuštění zadané hry
     */
    @Override
    public void startGame(IGame game)
    {
        if ((thisGame != null)  &&  (! game.isReady())) {
            int answer = JOptionPane.showConfirmDialog(appWindow,
                "V současné době je rozehrána hra\n\n" +
                thisGame.getClass().getName() +
                "\n\nOpravdu chcete tuto hru ukončit a začít hrát hru\n\n" +
                game.getClass());
            if (answer != 0) {
                return;
            }
            thisGame.stop();
        }
        if (game == null) {
            throw new NullPointerException(
                "\nMísto hry ke spuštění byl zadán prázdný odkaz");
        }else if (! game.isReady()) {
            throw new IllegalArgumentException(
                "\nZadaná hra je rozehrána. " + game.getClass() +
                "\nPřed dalším spuštěním je třeba hru nejprve ukončit");
        }
        thisGame = game;    //Zapamatuje si hru, která se bude hrát
        initializeGUI();    //Inicializuje GUI pro nově hranou hru
        executeCommand(""); //Ostartuje hru zadáním prádzného příkazu
    }


    /***************************************************************************
     * Spustí komunikaci mezi implicitní hrou a danou instancí GUI
     * mající na starosti komunikaci s uživatelem.
     */
    @Override
    public void startGame()
    {
        //Náhradní řešení: Spustím implicitní hru frameworku
        IGame choosedGame = _Test_115.getDefaultGame();
        startGame(choosedGame);
    }


    /***************************************************************************
     * Zpracuje příkaz předaný v parametru jako by byl zadán standardním
     * postupem z klávesnice. Metoda umožňuje testovat reakce daného
     * uživatelského rozhraní na příkazy zadávané z klávesnice.
     *
     * @param command Zadávaný příkaz
     */
    @Override
    public void executeCommand(String command)
    {
        //Zadá obdržený příkaz do vstupního pole jako zadaný text
        inputField.setText(command);

        //Zavolá metodu posluchače čekajícího na zadaní příkazu hry
        //Tato metoda zpracuje příkaz jako kdyby jej zadal hráč
        inputListener.actionPerformed(null);
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Inicializuje GUI (aplikační okno) tak, aby se mohla začít hrát nová hra.
     */
    private void initializeGUI()
    {
        //Postupně inicializuje jednotlivé panely, aniž by se starala,
        //jestli doopravdy potřebují inicializovat - to je jejich starost
        initializeInputPane();
        initializeOutputPane();
        initializeNeighborPane();
        initializeObjectPane();
        initializeBagPane();

        appWindow.pack();
        appWindow.setVisible(true);

        //Následující posunutí má smysl pouze tehdy,
        //cheme-li aplikační okno zobrazit na sekundárním monitoru
        appWindow.setLocation(POINT_0);
    }


    /***************************************************************************
     * Inicializuje oblast zobrazující objekty v batohu.
     */
    private void initializeBagPane()
    {
        bagPane.removeAll();
        bagPane.add(bagTitle);
    }


    /***************************************************************************
     * Inicializuje oblast zobrazující vstupní textové pole.
     */
    private void initializeInputPane()
    {
        //Tato oblast prozatím nepotřebuje zvláštní inicializaci
        //Metoda je definována především kvůli sjednocení přístupu
        //k jednotlivým panelům a z toho vyplývajícího zpřehlednění programu
        //Kdyby se v budoucnu něco změnilo, stačí doplnit její tělo
        //a není třeba procházet celým programem a zjišťovat,
        //kde by bylo vhodné chybějící metodu doplnit
    }


    /***************************************************************************
     * Inicializuje oblast zobrazující sousedy aktuálního prostoru.
     */
    private void initializeNeighborPane()
    {
        neighborPane.removeAll();
        neighborPane.add(neighborTitle);
    }


    /***************************************************************************
     * Inicializuje oblast zobrazující objekty v aktuálním prostoru.
     */
    private void initializeObjectPane()
    {
        objectPane.removeAll();
        objectPane.add(objectTitle);
    }


    /***************************************************************************
     * Inicializuje oblast s vystupujícím textem.
     * Zde je potřeba při každém spuštění hry smazat
     * případný dříve vypsaný text, aby hra začala s čistou oblastí.
     */
    private void initializeOutputPane()
    {
        outputArea.setText("");
        stringBuilder.setLength(0);
        stringBuilder.append("<html>");
    }


    /***************************************************************************
     * Připraví oblast určenou pro zobrazování objektů v batohu.
     */
    private void prepareBagPane()
    {
        //Příslušná komponenta je již vytvořena,
        //stačí ji přidat do aplikačního okna
        appWindow.add(bagPane, BorderLayout.NORTH);
    }


    /***************************************************************************
     * Připraví oblast se vstupním polem pro zadávání příkazů.
     */
    private void prepareInputPane()
    {
        inputField.addActionListener(inputListener);

        JPanel inputPanel = new JPanel();
        JLabel prompt     = new JLabel("Zadej příkaz: ");

        inputPanel.add(prompt);
        inputPanel.add(inputField);

        appWindow.add(inputPanel, BorderLayout.SOUTH);
    }


    /***************************************************************************
     * Připraví hlavní nabídku (menu bar).
     */
    private void prepareMenu()
    {
        JMenuBar menuBar = new JMenuBar();
        appWindow.setJMenuBar(menuBar);

        MenuGame_1 menuGame = new MenuGame_1(this);

        menuBar.add(menuGame);
    }


    /***************************************************************************
     * Připraví oblast určenou pro zobrazování sousedů aktuálního prostoru.
     */
    private void prepareNeighborPane()
    {
        //Příslušná komponenta je již vytvořena,
        //stačí ji přidat do aplikačního okna
        appWindow.add(neighborPane, BorderLayout.WEST);
    }


    /***************************************************************************
     * Připraví oblast určenou pro zobrazování objektů v aktuálním prostoru.
     */
    private void prepareObjectPane()
    {
        //Příslušná komponenta je již vytvořena,
        //stačí ji přidat do aplikačního okna
        appWindow.add(objectPane, BorderLayout.EAST);
    }


    /***************************************************************************
     * Připraví oblast, v níž bude zobrazován vystupující text.
     */
    private void prepareOutputPane()
    {
        outputArea.setEditable(false);

        //Vytvoří dekorátor, který v případě potřeby zařídí zobrazení posuvníků
        JScrollPane scroller = new JScrollPane(outputArea);
        scroller.setPreferredSize(OUTPUT_SIZE);

        //Do aplikačního okna se přidá příslušný dekorátor
        appWindow.add(scroller, BorderLayout.CENTER);
    }


    /***************************************************************************
     * Aktualizuje oblast zobrazující objekty v batohu.
     */
    private void updateBagPane()
    {
        initializeBagPane();
        IBag bag = thisGame.getBag();
        Collection<? extends IObject> objects = bag.getObjects();
        for (IObject object : objects) {
            bagPane.add(Box.createHorizontalStrut(VODOROVNÝ_ODSTUP));
            bagPane.add(new JLabel(object.getName()));
        }
//        bagPane.validate();
    }


    /***************************************************************************
     * Aktualizuje oblast zobrazující vstupní textové pole.
     * V této verzi stačí vstupní pole vyčistit a připravit pro další vstup.
     */
    private void updateInputPane()
    {
        inputField.setText("");
        inputField.requestFocus();
    }


    /***************************************************************************
     * Aktualizuje oblast zobrazující sousedy aktuálního prostoru.
     */
    private void updateNeighborPane()
    {
        initializeNeighborPane();

        IPlace actualPlace = thisGame.getActualPlace();
        Collection<? extends IPlace> neigbors = actualPlace.getNeighbors();
        for (IPlace place : neigbors) {
            neighborPane.add(Box.createVerticalStrut(SVISLÝ_ODSTUP));
            String neighborName = place.getName();
            JLabel placeLabel = new JLabel(neighborName);
            neighborPane.add(placeLabel);
        }
    }


    /***************************************************************************
     * Aktualizuje oblast zobrazující objekty v aktuálním prostoru.
     */
    private void updateObjectPane()
    {
        initializeObjectPane();

        IPlace actualPlace = thisGame.getActualPlace();
        Collection<? extends IObject> objects = actualPlace.getObjects();
        for (IObject object : objects) {
            objectPane.add(Box.createVerticalStrut(SVISLÝ_ODSTUP));
            String objectName = object.getName();
            JLabel objectLabel = new JLabel(objectName);
            objectPane.add(objectLabel);
        }
    }


    /***************************************************************************
     * Aktualizuje oblast s vystupujícím textem, tj.
     * přečte si zadaný příkaz a zapíše jej do výstupní oblasti,
     * zjistí, jak na tento příkaz hra zareaguje
     * a zapíše její odpověď do výstupní oblasti.
     */
    private void updateOutputPane()
    {
        String statement = inputField.getText();
        stringBuilder.append("<hr><b><font Color=red>").
                      append(statement).
                      append("</font></b><br>");
        String answer = thisGame.executeCommand(statement).
                                 replaceAll("\n", "<br>");
        String outputText = stringBuilder.append(answer).append("<br><br>") +
                            "<html>";
        outputArea.setText(outputText);

        Document document = outputArea.getDocument();
        int caretPosition = document.getLength();
        outputArea.setCaretPosition(caretPosition);
    }



//== INTERNÍ DATOVÉ TYPY =======================================================

    /***************************************************************************
     * Vnitřní třída, jejíž instance představují posluchače
     * vstupního textového pole čekající na stisk klávesy {@code Enter}
     * v okamžiku, kdy má pole fokus.
     */
    private class TxfListener implements ActionListener
    {
        /***********************************************************************
         * Zjistí aktuální text ve vstupním poli (= text zadaného příkazu),
         * zapíše jej do výstupní oblasti,
         * předá zadaný příkaz obsluhované hře ke zpracování
         * a do výstupního pole zapíše obdržený výsledek.
         *
         * @param e Objekt, který je schopen poskytnout informace o události,
         *          avšak v této verzi není využíván
         */
        @Override
        public void actionPerformed(ActionEvent e)
        {
            updateOutputPane();
            updateNeighborPane();
            updateObjectPane();
            updateBagPane();
            updateInputPane();

            appWindow.repaint();
            appWindow.validate();
        }
    }



//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda předvede hraní implicitní hry
     * prostřednictvím zde definovaného GUI.
     */
    public static void test()
    {
        GUI_03_1 gui   = new GUI_03_1();
        IGame    gameX = _Test_101.getDefaultGame();
        _Test_101.playGameByScenario(gameX, 2, gui);
    }
    /** @param args Parametry příkazového řádku - nepoužívané. */
    public static void main( String[] args )  {  test();  }
}
